package com.tuling.stu.service.order;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.tuling.stu.base.Constant;
import com.tuling.stu.base.model.RespEntity;
import com.tuling.stu.dao.order.OrderDao;
import com.tuling.stu.dao.order.TransationDao;
import com.tuling.stu.entity.order.TOrder;
import com.tuling.stu.entity.order.TTransation;

/**
 * 大型交易系统与银行对接核心代码
 * @author chenhs
 * Created on 2017年12月11日 上午11:14:13
 */
@Service
public class OrderServiceImpl implements OrderService {

	public static Logger logger = LoggerFactory
			.getLogger(OrderServiceImpl.class);

	@Autowired
	private OrderDao orderDao;
	@Autowired
	private TransationDao transationDao;
	
	@Autowired
	private BankService<?> bankService;
	
	@Autowired
	private TransactionTemplate transactionTemplate;

	/**
	 * 加锁方式 
	 * 1、redis（大并发） 
	 * 2、数据库锁: 
	 * 悲观锁【性能差，容易产生死锁 】:select * from table where id = ? for update
	 * 乐观锁【行锁】: 
	 * <1>基于版本号version实现 update table set status = ? where id = ? and version = ? 
	 * <2>基于状态机的实现 update table set status = ? where id= ? and status = 0
	 */
	
	/**
	 * 加入申请扣款流水表的原因：要解决银行系统调用的幂等问题。
	 * 银行系统请求非幂等性：它的表结构是以订单id为主键的，当如果一笔重复多次请求时会报primary key唯一性异常
	 * 所以要引入一个apply_order流水表，在状态机实现的乐观锁下，在流水表中apply_id与order_id会以一对一的方式
	 * 出现。
	 */
	
	/** Propagation.NEVER这里指阻断方法pay()被调用时产生的事务传播 */
	@Transactional(propagation = Propagation.NEVER)
	@Override
	public void pay(final TOrder order) {

		try {
			// 加锁
		final Long applyId = transactionTemplate.execute(new TransactionCallback<Long>() {
						@Override
						public Long doInTransaction(TransactionStatus status) {
							Long applyId = -1L;
							try {
								boolean lock = updateStatusById(order.getOrderNo(), Constant.ORDER_STATUS_DEALING, Constant.ORDER_STATUS_DEFAULT);
								if(lock){
									TTransation orderApply = new TTransation(order.getOrderNo(), order.getAmount(), Constant.ORDER_STATUS_SUCCESS, new Date());	//1表示未处理
									applyId = transationDao.insertOrder(orderApply);
								}
							} catch (Exception e) {
								status.setRollbackOnly();
								e.printStackTrace();
							}
							return applyId;
						}
					});

			// 获取到锁
			if (applyId < 0) {
				logger.info("锁失败！");
			} else {
				// 远程调用银行的扣款接口20s
				// 如果银行返回一个处理中或者超时？
				TOrder orderBank = new TOrder();
				//使用扣款申请表的流水号作为调用扣款的订单id
				orderBank.setOrderNo(applyId);
				orderBank.setAmount(order.getAmount());
				RespEntity<?> respEntity = bankService.outMoney(orderBank);	//银行系统非幂等【可重入】
				/**
				 * 一定有问题： 记录情况有：<1>respEntity!=null <2> key="0000"
				 */
				/*
				 * if(respEntity != null && "0000".equals(respEntity.getKey()))
				 * { //银行处理成功了 order.setStatus(Constant.ORDER_STATUS_SUCCESS); }
				 * else { //处理失败 order.setStatus(Constant.ORDER_STATUS_FAIL); }
				 */
				// 替代方案
				if (respEntity != null) {
					if ("0000".equals(respEntity.getKey())) {
						// 银行处理成功了
						order.setStatus(Constant.ORDER_STATUS_SUCCESS);
					} else {
						// 处理失败
						order.setStatus(Constant.ORDER_STATUS_FAIL);
					}
				} 

				// spring提供编程式事务
				transactionTemplate.execute(new TransactionCallback<Object>() {
					@Override
					public Object doInTransaction(TransactionStatus status) {
						try {
							TTransation apply = new TTransation();
							apply.setTranId(applyId);
							apply.setStatus(order.getStatus());		//此时扣款流水状态位与订单状态一致
							transationDao.updateApplyOrderStatus(apply);	
							orderDao.updateOrder(order);
						} catch (Exception e) {
							status.setRollbackOnly();
							e.printStackTrace();
						}
						return null;
					}
				});

			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Override
	public void updateOrder(TOrder o) throws Exception {
		orderDao.updateOrder(o);
	}

	@Override
	public boolean updateStatusById(Long id, Integer orainStatus,
			Integer newStatus) throws Exception {
		return orderDao.updateStatusById(id, orainStatus, newStatus);
	}

}
